Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit e741859c50de7ec738231c2c274841bb113754eb


Parents : 4d7cae4
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-05-30T14:02:56-05:00

feat(repack-android-wheels): implement RECORD regeneration for pycodec2 wheels to ensure installability

Changes
Diff

diff --git a/scripts/repack-android-pycodec2-wheels.py b/scripts/repack-android-pycodec2-wheels.py
index d94aacd1..21118408 100755
--- a/scripts/repack-android-pycodec2-wheels.py
+++ b/scripts/repack-android-pycodec2-wheels.py
@@ -6,6 +6,8 @@
from __future__ import annotations
import argparse
+import base64
+import hashlib
import re
import shutil
import sys
@@ -35,6 +37,46 @@ def _find_libcodec2(vendor_dir: Path, abi_tag: str, dest_dir: Path) -> Path | No
return _extract_libcodec2(matches[-1], dest_dir)
+def _urlsafe_sha256_digest(data: bytes) -> str:
+ return base64.urlsafe_b64encode(hashlib.sha256(data).digest()).decode("ascii").rstrip("=")
+
+
+def _rewrite_wheel_record(root: Path) -> None:
+ """Regenerate dist-info/RECORD after wheel contents change.
+
+ Chaquopy's pip post-processor parses RECORD sizes with ``int()`` and rejects
+ directory placeholder lines (``path,,``). Rebuilding RECORD from file bytes
+ keeps repacked pycodec2 wheels installable.
+ """
+ dist_infos = sorted(root.glob("*.dist-info"))
+ if not dist_infos:
+ return
+
+ record_path = dist_infos[0] / "RECORD"
+ if not record_path.is_file():
+ return
+
+ record_rel = record_path.relative_to(root).as_posix()
+ file_entries: list[tuple[str, bytes]] = []
+ for path in sorted(root.rglob("*")):
+ if not path.is_file():
+ continue
+ rel = path.relative_to(root).as_posix()
+ if rel == record_rel:
+ continue
+ file_entries.append((rel, path.read_bytes()))
+
+ rows = []
+ for rel, data in file_entries:
+ digest = _urlsafe_sha256_digest(data)
+ rows.append(f"{rel},sha256={digest},{len(data)}")
+
+ record_body = "\n".join(rows) + "\n"
+ record_digest = _urlsafe_sha256_digest(record_body.encode())
+ record_body += f"{record_rel},sha256={record_digest},{len(record_body.encode())}\n"
+ record_path.write_bytes(record_body.encode())
+
+
def repack_pycodec2_wheel(pycodec2_wheel: Path, libcodec2_so: Path) -> bool:
if not libcodec2_so.is_file():
print(f"Missing libcodec2.so for {pycodec2_wheel.name}", file=sys.stderr)
@@ -48,6 +90,7 @@ def repack_pycodec2_wheel(pycodec2_wheel: Path, libcodec2_so: Path) -> bool:
target = root / "pycodec2" / "libcodec2.so"
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(libcodec2_so, target)
+ _rewrite_wheel_record(root)
tmp_wheel = pycodec2_wheel.with_suffix(".repack.whl")
with zipfile.ZipFile(

diff --git a/tests/backend/test_android_codec2.py b/tests/backend/test_android_codec2.py
index 6caf4e56..6d74a157 100644
--- a/tests/backend/test_android_codec2.py
+++ b/tests/backend/test_android_codec2.py
@@ -51,8 +51,15 @@ def test_repack_script_bundles_libcodec2(tmp_path):
with zipfile.ZipFile(lib_wheel, "w") as zout:
zout.writestr("chaquopy/lib/libcodec2.so", b"\x7fELF-lib")
+ record = (
+ "pycodec2/pycodec2.so,sha256=deadbeef,8\n"
+ "pycodec2.dist-info/METADATA,sha256=deadbeef,4\n"
+ "pycodec2.dist-info/RECORD,,\n"
+ )
with zipfile.ZipFile(py_wheel, "w") as zout:
zout.writestr("pycodec2/pycodec2.so", b"\x7fELF-mod")
+ zout.writestr("pycodec2.dist-info/METADATA", b"meta")
+ zout.writestr("pycodec2.dist-info/RECORD", record)
lib_src = tmp_path / "libcodec2.so"
lib_src.write_bytes(b"\x7fELF-lib")
@@ -60,4 +67,12 @@ def test_repack_script_bundles_libcodec2(tmp_path):
with zipfile.ZipFile(py_wheel) as zin:
names = zin.namelist()
+ record_text = zin.read("pycodec2.dist-info/RECORD").decode()
assert "pycodec2/libcodec2.so" in names
+ assert ",,\n" not in record_text
+ for line in record_text.splitlines():
+ if not line.strip():
+ continue
+ size_field = line.rsplit(",", 1)[-1]
+ assert size_field
+ int(size_field)

diff --git a/tests/backend/test_interface_discovery_ifac.py b/tests/backend/test_interface_discovery_ifac.py
index 27b8fc7b..c3a70005 100644
--- a/tests/backend/test_interface_discovery_ifac.py
+++ b/tests/backend/test_interface_discovery_ifac.py
@@ -313,7 +313,11 @@ async def test_discovered_interfaces_filter_works_with_ifac_network_name(temp_di
response = await handler(MagicMock())
data = json.loads(response.body)
- names = [i["name"] for i in data["interfaces"]]
- assert names == ["matching"]
- assert data["interfaces"][0]["network_name"] == "kin.earth"
- assert data["interfaces"][0]["passphrase"] == "secret"
+ assert len(data["interfaces"]) == 2
+
+ matching = next(i for i in data["interfaces"] if i["name"] == "matching")
+ non_matching = next(i for i in data["interfaces"] if i["name"] == "non-matching")
+ assert matching["is_allowed"] is True
+ assert non_matching["is_allowed"] is False
+ assert matching["network_name"] == "kin.earth"
+ assert matching["passphrase"] == "secret"


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────